/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.startup;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;

import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Globals;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Realm;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.authenticator.NonLoginAuthenticator;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.core.NamingContextListener;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.security.SecurityClassLoad;
import org.apache.catalina.util.ContextName;
import org.apache.catalina.util.IOTools;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.UriUtil;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.file.ConfigFileLoader;
import org.apache.tomcat.util.file.ConfigurationSource;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;

// TODO: lazy init for the temp dir - only when a JSP is compiled or
// get temp dir is called we need to create it. This will avoid the
// need for the baseDir
// TODO: lazy init for temp dir -只有当JSP被编译或get temp dir被调用时，才需要创建它。
//  这将避免使用baseDir

// TODO: allow contexts without a base dir - i.e.
// only programmatic. This would disable the default servlet.
// TODO: 允许上下文没有基目录-即只有编程式。这将禁用默认servlet。

/**
 * Minimal tomcat starter for embedding/unit tests.
 *
 * <p>
 * Tomcat supports multiple styles of configuration and
 * startup - the most common and stable is server.xml-based,
 * implemented in org.apache.catalina.startup.Bootstrap.
 *
 * <p>
 * This class is for use in apps that embed tomcat.
 *
 * <p>
 * Requirements:
 * <ul>
 *   <li>all tomcat classes and possibly servlets are in the classpath.
 *       (for example all is in one big jar, or in eclipse CP, or in
 *        any other combination)</li>
 *
 *   <li>we need one temporary directory for work files</li>
 *
 *   <li>no config file is required. This class provides methods to
 *       use if you have a webapp with a web.xml file, but it is
 *       optional - you can use your own servlets.</li>
 * </ul>
 *
 * <p>
 * There are a variety of 'add' methods to configure servlets and webapps. These
 * methods, by default, create a simple in-memory security realm and apply it.
 * If you need more complex security processing, you can define a subclass of
 * this class.
 *
 * <p>
 * This class provides a set of convenience methods for configuring web
 * application contexts; all overloads of the method <code>addWebapp()</code>.
 * These methods are equivalent to adding a web application to the Host's
 * appBase (normally the webapps directory). These methods create a Context,
 * configure it with the equivalent of the defaults provided by
 * <code>conf/web.xml</code> (see {@link #initWebappDefaults(String)} for
 * details) and add the Context to a Host. These methods do not use a global
 * default web.xml; rather, they add a {@link LifecycleListener} to configure
 * the defaults. Any WEB-INF/web.xml and META-INF/context.xml packaged with the
 * application will be processed normally. Normal web fragment and
 * {@link javax.servlet.ServletContainerInitializer} processing will be applied.
 *
 * <p>
 * In complex cases, you may prefer to use the ordinary Tomcat API to create
 * webapp contexts; for example, you might need to install a custom Loader
 * before the call to {@link Host#addChild(Container)}. To replicate the basic
 * behavior of the <code>addWebapp</code> methods, you may want to call two
 * methods of this class: {@link #noDefaultWebXmlPath()} and
 * {@link #getDefaultWebXmlListener()}.
 *
 * <p>
 * {@link #getDefaultWebXmlListener()} returns a {@link LifecycleListener} that
 * adds the standard DefaultServlet, JSP processing, and welcome files. If you
 * add this listener, you must prevent Tomcat from applying any standard global
 * web.xml with ...
 *
 * <p>
 * {@link #noDefaultWebXmlPath()} returns a dummy pathname to configure to
 * prevent {@link ContextConfig} from trying to apply a global web.xml file.
 *
 * <p>
 * This class provides a main() and few simple CLI arguments,
 * see setters for doc. It can be used for simple tests and
 * demo.
 *
 * @see <a href="https://gitbox.apache.org/repos/asf?p=tomcat.git;a=blob;f=test/org/apache/catalina/startup/TestTomcat.java">TestTomcat</a>
 * @author Costin Manolache
 */
// 用于嵌入/单元测试的最小tomcat启动器。
// Tomcat支持多种配置和启动方式——最常见和最稳定的是基于server.xml的，在org.apache.catalina.startup.Bootstrap中实现。
// 这个类用于嵌入tomcat的应用程序。
// 要求: 所有tomcat类和servlet都在类路径中。
// (例如所有的都在一个大罐子里，或者在eclipse CP里，或者在任何其他的组合里)
// 我们需要一个临时目录来存放工作文件 不需要配置文件。
// 如果你有一个带有web.xml文件的webapp，这个类提供了可以使用的方法，但它是可选的——你可以使用你自己的servlet。
// 有各种各样的“添加”方法来配置servlet和webapps。
// 默认情况下，这些方法创建一个简单的内存安全域并应用它。
// 如果需要更复杂的安全处理，可以定义该类的子类。
// 这个类提供了一组方便的方法来配置web应用程序上下文;addWebapp()方法的所有重载。
// 这些方法等价于将一个web应用程序添加到主机的appBase(通常是webapps目录)。
// 这些方法创建一个Context，配置它与conf/web.xml提供的默认值等价(详见initWebappDefaults(String))，并将Context添加到主机。
// 这些方法不使用全局默认的web.xml;
// 相反，他们添加了一个LifecycleListener来配置默认值。
// 任何与应用程序打包的WEB-INF/web.xml和META-INF/context.xml都将正常处理。
// 正常的web片段和javax.servlet.ServletContainerInitializer处理将被应用。
// 在复杂的情况下，你可能更喜欢使用普通的Tomcat API来创建webapp上下文;
// 例如，你可能需要在调用Host.addChild(Container)之前安装一个自定义Loader。
// 为了复制addWebapp方法的基本行为，您可能需要调用该类的两个方法:
// noDefaultWebXmlPath()和getDefaultWebXmlListener()。
// getDefaultWebXmlListener()返回一个LifecycleListener，它添加了标准的DefaultServlet、JSP处理和欢迎文件。
// 如果你添加了这个监听器，你必须阻止Tomcat应用任何标准的全局web.xml…
// noDefaultWebXmlPath()返回一个要配置的虚拟路径名，以防止ContextConfig试图应用全局web.xml文件。
// 这个类提供了一个main()和一些简单的CLI参数，请参阅doc的setter。
// 它可以用于简单的测试和演示。
public class Tomcat {

    private static final StringManager sm = StringManager.getManager(Tomcat.class);

    // Some logging implementations use weak references for loggers so there is
    // the possibility that logging configuration could be lost if GC runs just
    // after Loggers are configured but before they are used. The purpose of
    // this Map is to retain strong references to explicitly configured loggers
    // so that configuration is not lost.
    // 一些日志实现对日志记录器使用弱引用，因此，如果GC在配置日志记录器之后但在使用它们之前运行，那么日志配置可能会丢失。
    // 这个Map的目的是保留对显式配置的记录器的强引用，这样配置就不会丢失。
    private final Map<String, Logger> pinnedLoggers = new HashMap<>();

    protected Server server;

    protected int port = 8080;
    protected String hostname = "localhost";
    protected String basedir;

    private final Map<String, String> userPass = new HashMap<>();
    private final Map<String, List<String>> userRoles = new HashMap<>();
    private final Map<String, Principal> userPrincipals = new HashMap<>();

    private boolean addDefaultWebXmlToWebapp = true;

    public Tomcat() {
        ExceptionUtils.preload();
    }

    /**
     * Tomcat requires that the base directory is set because the defaults for
     * a number of other locations, such as the work directory, are derived from
     * the base directory. This should be the first method called.
     * <p>
     * If this method is not called then Tomcat will attempt to use these
     * locations in the following order:
     * <ol>
     *  <li>if set, the catalina.base system property</li>
     *  <li>if set, the catalina.home system property</li>
     *  <li>The user.dir system property (the directory where Java was run from)
     *      where a directory named tomcat.$PORT will be created. $PORT is the
     *      value configured via {@link #setPort(int)} which defaults to 8080 if
     *      not set</li>
     * </ol>
     * The user should ensure that the file permissions for the base directory
     * are appropriate.
     * <p>
     * TODO: disable work dir if not needed ( no jsp, etc ).
     *
     * @param basedir The Tomcat base folder on which all others will be derived
     */
    // Tomcat要求设置基本目录，因为许多其他位置(比如工作目录)的默认值都是从基本目录派生出来的。
    // 这应该是第一个被调用的方法。
    // 如果这个方法没有被调用，那么Tomcat将按照以下顺序尝试使用这些位置:
    // catalina.base
    // catalina.home
    // user.dir (运行Java的目录)
    // 会在找到的目录下创建一个铭文tomcat.port的目录
    // $PORT是通过setPort(int)配置的值，如果没有设置，默认为8080
    // 用户应确保基本目录的文件权限是适当的。
    // 待办事项:如果不需要禁用工作目录(没有jsp等)。
    // 参数: basedir - Tomcat基本文件夹，所有其他文件夹都将在其上派生
    public void setBaseDir(String basedir) {
        this.basedir = basedir;
    }

    /**
     * Set the port for the default connector. The default connector will
     * only be created if getConnector is called.
     * @param port The port number
     */
    // 为默认连接器设置端口。只有在调用getConnector时才会创建默认连接器
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * The the hostname of the default host, default is
     * 'localhost'.
     * @param s The default host name
     */
    // 默认主机的主机名，默认为'localhost'。
    public void setHostname(String s) {
        hostname = s;
    }


    /**
     * This is equivalent to adding a web application to a Host's appBase
     * (usually Tomcat's webapps directory). By default, the equivalent of the
     * default web.xml will be applied to the web application (see
     * {@link #initWebappDefaults(String)}). This may be prevented by calling
     * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any
     * <code>WEB-INF/web.xml</code> and <code>META-INF/context.xml</code>
     * packaged with the application will always be processed and normal web
     * fragment and {@link javax.servlet.ServletContainerInitializer} processing
     * will always be applied.
     *
     * @param contextPath The context mapping to use, "" for root context.
     * @param docBase     Base directory for the context, for static files. Must
     *                        exist, relative to the server home
     *
     * @return the deployed context
     */
    // 这相当于将一个web应用程序添加到主机的app基础目录(通常是Tomcat的webapps目录)。
    // 默认情况下，认web.xml将应用于web应用程序(参见initWebappDefaults(String))，
    // 但是可以通过调用setAddDefaultWebXmlToWebapp(boolean)为false来防止。
    // 任何与应用程序打包的WEB-INF/web.xml和META-INF/context.xml将始终被处理，
    // 正常的web片段和javax.servlet.ServletContainerInitializer处理将始终被应用。
    // 参数: contextPath—上下文映射，使用“”作为根上下文。
    // docBase -上下文的基本目录，用于静态文件，必须存在，连接到服务目录
    // 返回: 部署的上下文
    public Context addWebapp(String contextPath, String docBase) {
        return addWebapp(getHost(), contextPath, docBase);
    }


    /**
     * Copy the specified WAR file to the Host's appBase and then call
     * {@link #addWebapp(String, String)} with the newly copied WAR. The WAR
     * will <b>NOT</b> be removed from the Host's appBase when the Tomcat
     * instance stops. Note that {@link ExpandWar} provides utility methods that
     * may be used to delete the WAR and/or expanded directory if required.
     *
     * @param contextPath   The context mapping to use, "" for root context.
     * @param source        The location from which the WAR should be copied
     *
     * @return The deployed Context
     *
     * @throws IOException If an I/O error occurs while copying the WAR file
     *                     from the specified URL to the appBase
     */
    // 将指定的WAR文件复制到主机的appBase，然后使用新复制的WAR调用addWebapp(String, String)。
    // 当Tomcat实例停止时，WAR将不会从主机的appBase中删除。
    // 注意，ExpandWar提供了实用程序方法，如果需要，可以使用这些方法删除WAR和/或扩展目录。
    // 参数: contextPath—使用“”作为根上下文的上下文映射。
    // source - WAR应该被复制的位置
    // 返回: 部署的容器
    // 抛出: IOException -当从指定URL复制WAR文件到appBase时发生I/O错误
    public Context addWebapp(String contextPath, URL source) throws IOException {

        ContextName cn = new ContextName(contextPath, null);

        // Make sure a conflicting web application has not already been deployed
        // 确保没有部署冲突的web应用程序
        Host h = getHost();
        if (h.findChild(cn.getName()) != null) {
            // 如果已经部署过这个应用了，抛出异常
            throw new IllegalArgumentException(sm.getString("tomcat.addWebapp.conflictChild",
                    source, contextPath, cn.getName()));
        }

        // Make sure appBase does not contain a conflicting web application
        // 确保appBase不包含冲突的web应用程序
        File targetWar = new File(h.getAppBaseFile(), cn.getBaseName() + ".war");
        File targetDir = new File(h.getAppBaseFile(), cn.getBaseName());

        // 如果对应名称的war包已经存在
        if (targetWar.exists()) {
            throw new IllegalArgumentException(sm.getString("tomcat.addWebapp.conflictFile",
                    source, contextPath, targetWar.getAbsolutePath()));
        }
        // 如果对应名称的目录或文件已经存在
        if (targetDir.exists()) {
            throw new IllegalArgumentException(sm.getString("tomcat.addWebapp.conflictFile",
                    source, contextPath, targetDir.getAbsolutePath()));
        }

        // Should be good to copy the WAR now
        // 现在可以放心的复制war包了
        URLConnection uConn = source.openConnection();

        // 获取输入流和输出流
        try (InputStream is = uConn.getInputStream();
                OutputStream os = new FileOutputStream(targetWar)) {
            // 将输入流复制到输出流，生成War文件
            IOTools.flow(is, os);
        }

        // 现在war包已经准备好了
        return addWebapp(contextPath, targetWar.getAbsolutePath());
    }


    /**
     * Add a context - programmatic mode, no default web.xml used. This means
     * that there is no JSP support (no JSP servlet), no default servlet and
     * no web socket support unless explicitly enabled via the programmatic
     * interface. There is also no
     * {@link javax.servlet.ServletContainerInitializer} processing and no
     * annotation processing. If a
     * {@link javax.servlet.ServletContainerInitializer} is added
     * programmatically, there will still be no scanning for
     * {@link javax.servlet.annotation.HandlesTypes} matches.
     *
     * <p>
     * API calls equivalent with web.xml:
     *
     * <pre>{@code
     *  // context-param
     *  ctx.addParameter("name", "value");
     *
     *
     *  // error-page
     *  ErrorPage ep = new ErrorPage();
     *  ep.setErrorCode(500);
     *  ep.setLocation("/error.html");
     *  ctx.addErrorPage(ep);
     *
     *  ctx.addMimeMapping("ext", "type");
     * }</pre>
     *
     *
     * <p>
     * Note: If you reload the Context, all your configuration will be lost. If
     * you need reload support, consider using a LifecycleListener to provide
     * your configuration.
     *
     * <p>
     * TODO: add the rest
     *
     * @param contextPath The context mapping to use, "" for root context.
     * @param docBase Base directory for the context, for static files.
     *  Must exist, relative to the server home
     * @return the deployed context
     */
    // 添加一个上下文编程模式，不使用默认的web.xml。
    // 这意味着没有JSP支持(没有JSP servlet)，没有默认servlet，也没有web套接字支持，除非通过编程接口显式启用。
    // 也没有javax.servlet.ServletContainerInitializer处理和注释处理。
    // 如果以编程方式添加javax.servlet.ServletContainerInitializer
    // ，仍然不会扫描匹配的javax.servlet.annotation. handlesttypes。
    // API调用等价于web.xml:
    // / / context-param ctx。addParameter(“名字”,“价值”);
    // / /页面偷走了 ErrorPage ep = new ErrorPage();
    // ep.setErrorCode (500);
    // ep.setLocation (" / error.html ");
    // ctx.addErrorPage (ep);
    // ctx。addMimeMapping(“ext”、“类型”);
    // 注意:如果您重新加载Context，您的所有配置都将丢失。
    // 如果您需要重新加载支持，可以考虑使用LifecycleListener来提供您的配置。
    // 待办事项:添加其余的   参数: contextPath—使用“”作为根上下文的上下文映射。
    // docBase -上下文的基本目录，用于静态文件。必须存在，相对于服务器家 返回: 部署的上下文
    public Context addContext(String contextPath, String docBase) {
        return addContext(getHost(), contextPath, docBase);
    }

    /**
     * Equivalent to &lt;servlet&gt;&lt;servlet-name&gt;&lt;servlet-class&gt;.
     *
     * <p>
     * In general it is better/faster to use the method that takes a
     * Servlet as param - this one can be used if the servlet is not
     * commonly used, and want to avoid loading all deps.
     * ( for example: jsp servlet )
     *
     * You can customize the returned servlet, ex:
     *  <pre>
     *    wrapper.addInitParameter("name", "value");
     *  </pre>
     *
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    // 等价于
    // servlet
    // servlet-name
    // servlet-class。
    // 一般来说，使用以Servlet作为参数的方法更好/更快
    // 如果Servlet不常用，并且希望避免加载所有deps，可以使用这个方法。
    // (例如:jsp servlet)
    // 您可以自定义返回的servlet，例如:
    // wrapper.addInitParameter("name", "value");
    // 参数: contextPath—要添加Servlet的上下文 servletName——Servlet名称(用于映射)
    // servletClass——用于Servlet的类 返回: servlet的包装器
    public Wrapper addServlet(String contextPath,
            String servletName,
            String servletClass) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servletClass);
    }

    /**
     * Static version of {@link #addServlet(String, String, String)}
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servletClass  The class to be used for the Servlet
     * @return The wrapper for the servlet
     */
    // 静态版本的addServlet(字符串，字符串，字符串)
    // 参数:
    // ctx -要添加Servlet的上下文
    // servletName——Servlet名称(用于映射)
    // servletClass——用于Servlet的类
    // 返回: servlet的包装器
    public static Wrapper addServlet(Context ctx,
                                      String servletName,
                                      String servletClass) {
        // will do class for name and set init params
        // 将做类的名称和设置初始化参数
        Wrapper sw = ctx.createWrapper();
        sw.setServletClass(servletClass);
        sw.setName(servletName);
        ctx.addChild(sw);

        return sw;
    }

    /**
     * Add an existing Servlet to the context with no class.forName or
     * initialisation.
     * @param contextPath   Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    // 将一个现有的Servlet添加到这个的上下文中。没有用class.forName或初始化。
    // 参数: contextPath—要添加Servlet的上下文
    // servletName——Servlet名称(用于映射)
    // servlet -要添加的servlet
    // 返回: servlet的包装器
    public Wrapper addServlet(String contextPath,
            String servletName,
            Servlet servlet) {
        Container ctx = getHost().findChild(contextPath);
        return addServlet((Context) ctx, servletName, servlet);
    }

    /**
     * Static version of {@link #addServlet(String, String, Servlet)}.
     * @param ctx           Context to add Servlet to
     * @param servletName   Servlet name (used in mappings)
     * @param servlet       The Servlet to add
     * @return The wrapper for the servlet
     */
    // 静态版本的addServlet(字符串，字符串，Servlet)。
    // 参数:
    // ctx -要添加Servlet的上下文
    // servletName——Servlet名称(用于映射)
    // servlet -要添加的servlet 返回: servlet的包装器
    public static Wrapper addServlet(Context ctx,
                                      String servletName,
                                      Servlet servlet) {
        // will do class for name and set init params
        // 将做类的名称和设置初始化参数
        Wrapper sw = new ExistingStandardWrapper(servlet);
        sw.setName(servletName);
        ctx.addChild(sw);

        return sw;
    }


    /**
     * Initialize the server given the specified configuration source.
     * The server will be loaded according to the Tomcat configuration
     * files contained in the source (server.xml, web.xml, context.xml,
     * SSL certificates, etc).
     * If no configuration source is specified, it will use the default
     * locations for these files.
     * @param source The configuration source
     */
    // 根据指定的配置源初始化服务器。
    // 服务器将根据源文件中包含的Tomcat配置文件(server.xml、web.xml、context.xml、SSL证书等)加载。
    // 如果没有指定配置源，它将使用这些文件的默认位置。
    public void init(ConfigurationSource source) {
        init(source, null);
    }

    /**
     * Initialize the server given the specified configuration source.
     * The server will be loaded according to the Tomcat configuration
     * files contained in the source (server.xml, web.xml, context.xml,
     * SSL certificates, etc).
     * If no configuration source is specified, it will use the default
     * locations for these files.
     * @param source The configuration source
     * @param catalinaArguments The arguments that should be passed to Catalina
     */
    // 根据指定的配置源初始化服务器。
    // 服务器将根据源文件中包含的Tomcat配置文件(server.xml、web.xml、context.xml、SSL证书等)加载。
    // 如果没有指定配置源，它将使用这些文件的默认位置。
    // 参数: source -配置源
    // catalinaArguments -应该传递给Catalina的参数
    public void init(ConfigurationSource source, String[] catalinaArguments) {
        ConfigFileLoader.setSource(source);// 配置文件加载器
        addDefaultWebXmlToWebapp = false;
        Catalina catalina = new Catalina();
        // Load the Catalina instance with the regular configuration files
        // from specified source
        // 使用指定源的常规配置文件加载Catalina实例
        if (catalinaArguments == null) {
            catalina.load();
        } else {
            catalina.load(catalinaArguments);
        }
        // Retrieve and set the server
        // 检索并设置服务器
        server = catalina.getServer();
    }


    /**
     * Initialize the server.
     *
     * @throws LifecycleException Init error
     */
    // 初始化服务器。
    // 抛出: LifecycleException—初始化错误
    public void init() throws LifecycleException {
        getServer();
        server.init();
    }


    /**
     * Start the server.
     *
     * @throws LifecycleException Start error
     */
    // 启动服务器。
    // 抛出: LifecycleException -启动错误
    public void start() throws LifecycleException {
        getServer();
        server.start();
    }

    /**
     * Stop the server.
     *
     * @throws LifecycleException Stop error
     */
    // 停止服务
    public void stop() throws LifecycleException {
        getServer();
        server.stop();
    }


    /**
     * Destroy the server. This object cannot be used once this method has been
     * called.
     *
     * @throws LifecycleException Destroy error
     */
    // 销毁服务器。一旦调用此方法，就不能使用此对象。
    // 抛出: LifecycleException -销毁错误
    public void destroy() throws LifecycleException {
        getServer();
        server.destroy();
        // Could null out objects here
    }

    /**
     * Add a user for the in-memory realm. All created apps use this
     * by default, can be replaced using setRealm().
     * @param user The user name
     * @param pass The password
     */
    // 为内存领域添加一个用户。所有创建的应用都默认使用这个，可以使用setRealm()替换。
    // 参数: user -用户名
    // pass -密码
    public void addUser(String user, String pass) {
        userPass.put(user, pass);
    }

    /**
     * Add a role to a user.
     * @see #addUser(String, String)
     * @param user The user name
     * @param role The role name
     */
    // 为用户添加角色。
    // 参数: user -用户名
    // role -角色名
    public void addRole(String user, String role) {
        userRoles.computeIfAbsent(user, k -> new ArrayList<>()).add(role);
    }

    // ------- Extra customization -------
    // You can tune individual Tomcat objects, using internal APIs
    // ------- 额外的定制 -------
    // 您可以使用内部api优化各个Tomcat对象

    /**
     * Get the default HTTP connector that is used by the embedded
     * Tomcat. It is first configured connector in the service.
     * If there's no connector defined, it will create and add a default
     * connector using the port and address specified in this Tomcat
     * instance, and return it for further customization.
     *
     * @return The connector object
     */
    // 获取嵌入式Tomcat使用的默认HTTP连接器。
    // 它是服务中第一个配置的连接器。
    // 如果没有定义连接器，它将使用这个Tomcat实例中指定的端口和地址创建和添加一个默认连接器，并返回它以供进一步定制。
    // 返回: 连接器对象
    public Connector getConnector() {
        Service service = getService();
        if (service.findConnectors().length > 0) {
            return service.findConnectors()[0];
        }
        // The same as in standard Tomcat configuration.
        // This creates an APR HTTP connector if AprLifecycleListener has been
        // configured (created) and Tomcat Native library is available.
        // Otherwise it creates a NIO HTTP connector.
        // 与标准Tomcat配置相同。
        // 如果AprLifecycleListener已经配置(创建)，并且Tomcat Native库可用，这将创建APR HTTP连接器。
        // 否则，它将创建一个NIO HTTP连接器。
        // 创建一个新的连接器，创建时会创建新的协议处理器和EndPoint
        Connector connector = new Connector("HTTP/1.1");
        connector.setPort(port);
        service.addConnector(connector);
        return connector;
    }

    /**
     * Set the specified connector in the service, if it is not already
     * present.
     * @param connector The connector instance to add
     */
    // 如果服务中还没有指定的连接器，则设置该连接器。
    // 参数: connector -要添加的连接器实例
    public void setConnector(Connector connector) {
        Service service = getService();
        boolean found = false;
        for (Connector serviceConnector : service.findConnectors()) {
            if (connector == serviceConnector) {
                found = true;
                break;
            }
        }
        if (!found) {
            service.addConnector(connector);
        }
    }

    /**
     * Get the service object. Can be used to add more
     * connectors and few other global settings.
     * @return The service
     */
    // 获取服务对象。可用于添加更多连接器和少数其他全局设置。
    // 返回: 服务
    public Service getService() {
        return getServer().findServices()[0];
    }

    /**
     * Sets the current host - all future webapps will
     * be added to this host. When tomcat starts, the
     * host will be the default host.
     *
     * @param host The current host
     */
    // 设置当前主机-所有未来的web应用程序将被添加到这个主机。当tomcat启动时，该主机将是默认主机。
    public void setHost(Host host) {
        Engine engine = getEngine();
        boolean found = false;
        for (Container engineHost : engine.findChildren()) {
            if (engineHost == host) {
                found = true;
                break;
            }
        }
        if (!found) {
            engine.addChild(host);
        }
    }

    public Host getHost() {
        Engine engine = getEngine();
        if (engine.findChildren().length > 0) {
            return (Host) engine.findChildren()[0];
        }

        Host host = new StandardHost();
        host.setName(hostname);
        getEngine().addChild(host);
        return host;
    }

    /**
     * Access to the engine, for further customization.
     * @return The engine
     */
    // 访问引擎，以便进一步定制。
    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }

    /**
     * Get the server object. You can add listeners and few more
     * customizations. JNDI is disabled by default.
     * @return The Server
     */
    // 获取服务器对象。
    // 您可以添加侦听器和其他一些自定义。JNDI默认情况下是禁用的。
    // 返回: 服务器
    public Server getServer() {
        // 如果已经有server对象，直接返回
        if (server != null) {
            return server;
        }

        // 禁用JNDI
        System.setProperty("catalina.useNaming", "false");

        server = new StandardServer();

        initBaseDir();

        // Set configuration source
        // 设置配置源
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

        server.setPort( -1 );

        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }

    /**
     * @param host The host in which the context will be deployed
     * @param contextPath The context mapping to use, "" for root context.
     * @param dir Base directory for the context, for static files.
     *  Must exist, relative to the server home
     * @return the deployed context
     * @see #addContext(String, String)
     */
    //参数:
    // host -将部署上下文的主机
    // contextPath—使用“”作为根上下文的上下文映射。
    // dir -上下文的基本目录，用于静态文件。必须存在，相对于服务器家
    // 返回: 部署的上下文
    // 参见: addContext(字符串,字符串)
    public Context addContext(Host host, String contextPath, String dir) {
        return addContext(host, contextPath, contextPath, dir);
    }

    /**
     * @param host The host in which the context will be deployed
     * @param contextPath The context mapping to use, "" for root context.
     * @param contextName The context name
     * @param dir Base directory for the context, for static files.
     *  Must exist, relative to the server home
     * @return the deployed context
     * @see #addContext(String, String)
     */
    // 参数: host -将部署上下文的主机 contextPath—使用“”作为根上下文的上下文映射。
    // contextName—上下文名称 dir -上下文的基本目录，用于静态文件。
    // 必须存在，相对于服务器家 返回: 部署的上下文 参见: addContext(字符串,字符串)
    public Context addContext(Host host, String contextPath, String contextName,
            String dir) {
        silence(host, contextName);
        Context ctx = createContext(host, contextPath);
        ctx.setName(contextName);
        ctx.setPath(contextPath);
        ctx.setDocBase(dir);
        ctx.addLifecycleListener(new FixContextListener());

        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }
        return ctx;
    }


    /**
     * This is equivalent to adding a web application to a Host's appBase
     * (usually Tomcat's webapps directory). By default, the equivalent of the
     * default web.xml will be applied to the web application (see
     * {@link #initWebappDefaults(String)}). This may be prevented by calling
     * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any
     * <code>WEB-INF/web.xml</code> and <code>META-INF/context.xml</code>
     * packaged with the application will always be processed and normal web
     * fragment and {@link javax.servlet.ServletContainerInitializer} processing
     * will always be applied.
     *
     * @param host        The host in which the context will be deployed
     * @param contextPath The context mapping to use, "" for root context.
     * @param docBase     Base directory for the context, for static files. Must
     *                        exist, relative to the server home
     *
     * @return the deployed context
     */
    // 这相当于将一个web应用程序添加到主机的app基础目录(通常是Tomcat的webapps目录)。
    // 默认情况下，认web.xml将应用于web应用程序(参见initWebappDefaults(String))，
    // 但是可以通过调用setAddDefaultWebXmlToWebapp(boolean)为false来防止。
    // 任何与应用程序打包的WEB-INF/web.xml和META-INF/context.xml将始终被处理，
    // 正常的web片段和javax.servlet.ServletContainerInitializer处理将始终被应用。
    // 参数: contextPath—上下文映射，使用“”作为根上下文。
    // docBase -上下文的基本目录，用于静态文件，必须存在，连接到服务目录
    // 返回: 部署的上下文
    public Context addWebapp(Host host, String contextPath, String docBase) {
        LifecycleListener listener = null;
        try {
            // 反射获取类
            Class<?> clazz = Class.forName(getHost().getConfigClass());
            listener = (LifecycleListener) clazz.getConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            // Wrap in IAE since we can't easily change the method signature to
            // to throw the specific checked exceptions
            // 包装在IAE中，因为我们不能轻易地将方法签名更改为抛出特定的检查异常
            throw new IllegalArgumentException(e);
        }

        return addWebapp(host, contextPath, docBase, listener);
    }


    /**
     * This is equivalent to adding a web application to a Host's appBase
     * (usually Tomcat's webapps directory). By default, the equivalent of the
     * default web.xml will be applied to the web application (see
     * {@link #initWebappDefaults(String)}). This may be prevented by calling
     * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any
     * <code>WEB-INF/web.xml</code> and <code>META-INF/context.xml</code>
     * packaged with the application will always be processed and normal web
     * fragment and {@link javax.servlet.ServletContainerInitializer} processing
     * will always be applied.
     *
     * @param host        The host in which the context will be deployed
     * @param contextPath The context mapping to use, "" for root context.
     * @param docBase     Base directory for the context, for static files. Must
     *                        exist, relative to the server home
     * @param config      Custom context configuration helper. Any configuration
     *                        will be in addition to equivalent of the default
     *                        web.xml configuration described above.
     *
     * @return the deployed context
     */
    // 这相当于将一个web应用程序添加到主机的appBase(通常是Tomcat的webapps目录)。
    // 默认情况下，等价的默认web.xml将应用于web应用程序(参见initWebappDefaults(String))。
    // 这可以通过调用setAddDefaultWebXmlToWebapp(boolean)和false来防止。
    // 任何与应用程序打包的web - inf /web.xml和META-INF/context.xml将始终被处理，正常的web片段和javax.servlet.ServletContainerInitializer处理将始终被应用。
    // 参数: host -将部署上下文的主机 contextPath—使用“”作为根上下文的上下文映射。
    // docBase -上下文的基本目录，用于静态文件。必须存在，相对于服务器家 config -自定义上下文配置助手。
    // 任何配置都将与上面描述的默认web.xml配置等效。 返回: 部署的上下文
    public Context addWebapp(Host host, String contextPath, String docBase,
            LifecycleListener config) {

        silence(host, contextPath);

        Context ctx = createContext(host, contextPath);
        ctx.setPath(contextPath);
        ctx.setDocBase(docBase);

        if (addDefaultWebXmlToWebapp) {
            ctx.addLifecycleListener(getDefaultWebXmlListener());
        }

        ctx.setConfigFile(getWebappConfigFile(docBase, contextPath));

        ctx.addLifecycleListener(config);

        if (addDefaultWebXmlToWebapp && (config instanceof ContextConfig)) {
            // prevent it from looking ( if it finds one - it'll have dup error )
            // 阻止它寻找(如果它找到一个-它会有dup错误)
            ((ContextConfig) config).setDefaultWebXml(noDefaultWebXmlPath());
        }

        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }

        return ctx;
    }

    /**
     * Return a listener that provides the required configuration items for JSP
     * processing. From the standard Tomcat global web.xml. Pass this to
     * {@link Context#addLifecycleListener(LifecycleListener)} and then pass the
     * result of {@link #noDefaultWebXmlPath()} to
     * {@link ContextConfig#setDefaultWebXml(String)}.
     * @return a listener object that configures default JSP processing.
     */
    // 返回一个侦听器，它为JSP处理提供所需的配置项。
    // 从标准的Tomcat全局web.xml。
    // 将此传递给Context.addLifecycleListener(LifecycleListener)，然后将noDefaultWebXmlPath()的结果传递给ContextConfig.setDefaultWebXml(String)。
    // 返回: 配置默认JSP处理的侦听器对象。
    public LifecycleListener getDefaultWebXmlListener() {
        return new DefaultWebXmlListener();
    }

    /**
     * @return a pathname to pass to
     * {@link ContextConfig#setDefaultWebXml(String)} when using
     * {@link #getDefaultWebXmlListener()}.
     */
    // 返回: 使用getDefaultWebXmlListener()时传递给ContextConfig.setDefaultWebXml(String)的路径名。
    public String noDefaultWebXmlPath() {
        return Constants.NoDefaultWebXml;
    }

    // ---------- Helper methods and classes -------------------

    /**
     * Create an in-memory realm. You can replace it for contexts with a real
     * one. The Realm created here will be added to the Engine by default and
     * may be replaced at the Engine level or over-ridden (as per normal Tomcat
     * behaviour) at the Host or Context level.
     * @return a realm instance
     */
    // 创建内存领域。
    // 您可以用一个真实的上下文替换它。
    // 在这里创建的域将默认添加到引擎，并可能在引擎级别被替换，或者在主机或上下文级别被覆盖(根据Tomcat的正常行为)。
    // 返回: 一个领域实例
    protected Realm createDefaultRealm() {
        return new SimpleRealm();
    }


    private class SimpleRealm extends RealmBase {

        @Override
        protected String getPassword(String username) {
            return userPass.get(username);
        }

        @Override
        protected Principal getPrincipal(String username) {
            Principal p = userPrincipals.get(username);
            if (p == null) {
                String pass = userPass.get(username);
                if (pass != null) {
                    p = new GenericPrincipal(username, pass,
                            userRoles.get(username));
                    userPrincipals.put(username, p);
                }
            }
            return p;
        }
    }


    protected void initBaseDir() {
        String catalinaHome = System.getProperty(Globals.CATALINA_HOME_PROP);
        if (basedir == null) {
            basedir = System.getProperty(Globals.CATALINA_BASE_PROP);
        }
        if (basedir == null) {
            basedir = catalinaHome;
        }
        if (basedir == null) {
            // Create a temp dir.
            // 创建一个临时目录。
            basedir = System.getProperty("user.dir") + "/tomcat." + port;
        }

        File baseFile = new File(basedir);
        if (baseFile.exists()) {
            if (!baseFile.isDirectory()) {
                throw new IllegalArgumentException(sm.getString("tomcat.baseDirNotDir", baseFile));
            }
        } else {
            if (!baseFile.mkdirs()) {
                // Failed to create base directory
                // 创建基目录失败。处理步骤
                throw new IllegalStateException(sm.getString("tomcat.baseDirMakeFail", baseFile));
            }
            /*
             * If file permissions were going to be set on the newly created
             * directory, this is the place to do it. However, even simple
             * calls such as File.setReadable(boolean,boolean) behaves
             * differently on different platforms. Therefore, setBaseDir
             * documents that the user needs to do this.
             */
            /*
             * 如果要在新创建的目录上设置文件权限，则可以在这里进行设置。
             * 然而，即使是简单的调用File.setReadable(boolean,boolean)在不同平台上的行为也不同
             * 。因此，setBaseDir文档说明用户需要这样做。
             */
        }
        try {
            baseFile = baseFile.getCanonicalFile();
        } catch (IOException e) {
            baseFile = baseFile.getAbsoluteFile();
        }
        server.setCatalinaBase(baseFile);
        System.setProperty(Globals.CATALINA_BASE_PROP, baseFile.getPath());
        basedir = baseFile.getPath();

        if (catalinaHome == null) {
            server.setCatalinaHome(baseFile);
        } else {
            File homeFile = new File(catalinaHome);
            if (!homeFile.isDirectory() && !homeFile.mkdirs()) {
                // Failed to create home directory
                // 创建主目录失败
                throw new IllegalStateException(sm.getString("tomcat.homeDirMakeFail", homeFile));
            }
            try {
                homeFile = homeFile.getCanonicalFile();
            } catch (IOException e) {
                homeFile = homeFile.getAbsoluteFile();
            }
            server.setCatalinaHome(homeFile);
        }
        System.setProperty(Globals.CATALINA_HOME_PROP,
                server.getCatalinaHome().getPath());
    }

    static final String[] silences = new String[] {
        "org.apache.coyote.http11.Http11NioProtocol",
        "org.apache.catalina.core.StandardService",
        "org.apache.catalina.core.StandardEngine",
        "org.apache.catalina.startup.ContextConfig",
        "org.apache.catalina.core.ApplicationContext",
        "org.apache.catalina.core.AprLifecycleListener"
    };

    private boolean silent = false;

    /**
     * Controls if the loggers will be silenced or not.
     * @param silent    <code>true</code> sets the log level to WARN for the
     *                  loggers that log information on Tomcat start up. This
     *                  prevents the usual startup information being logged.
     *                  <code>false</code> sets the log level to the default
     *                  level of INFO.
     */
    // 控制记录器是否被静音。
    // 参数: silent - true设置Tomcat启动日志信息的日志记录器的日志级别为WARN。
    // 这可以防止通常的启动信息被记录。
    // false将日志级别设置为默认的INFO级别。
    public void setSilent(boolean silent) {
        this.silent = silent;
        for (String s : silences) {
            Logger logger = Logger.getLogger(s);
            pinnedLoggers.put(s, logger);
            if (silent) {
                logger.setLevel(Level.WARNING);
            } else {
                logger.setLevel(Level.INFO);
            }
        }
    }

    private void silence(Host host, String contextPath) {
        String loggerName = getLoggerName(host, contextPath);
        Logger logger = Logger.getLogger(loggerName);
        pinnedLoggers.put(loggerName, logger);
        if (silent) {
            logger.setLevel(Level.WARNING);
        } else {
            logger.setLevel(Level.INFO);
        }
    }


    /**
     * By default, when calling addWebapp() to create a Context, the settings from
     * from the default web.xml are added to the context.  Calling this method with
     * a <code>false</code> value prior to calling addWebapp() allows to opt out of
     * the default settings. In that event you will need to add the configurations
     * yourself,  either programmatically or by using web.xml deployment descriptors.
     * @param addDefaultWebXmlToWebapp <code>false</code> will prevent the class from
     *                                 automatically adding the default settings when
     *                                 calling addWebapp().
     *                                 <code>true</code> will add the default settings
     *                                 and is the default behavior.
     * @see #addWebapp(Host, String, String, LifecycleListener)
     */
    // 默认情况下，当调用addWebapp()创建Context时，默认的web.xml中的设置会被添加到Context中。
    // 在调用addWebapp()之前使用假值调用此方法允许选择退出默认设置。
    // 在这种情况下，您需要自己添加配置，可以通过编程方式，也可以使用web.xml部署描述符。
    // 参数: addDefaultWebXmlToWebapp - false将阻止类在调用addWebapp()时自动添加默认设置。
    // True将添加默认设置，并且是默认行为。
    // 参见: addWebapp(主机，字符串，字符串，生命周期监听器)
    public void setAddDefaultWebXmlToWebapp(boolean addDefaultWebXmlToWebapp){
        this.addDefaultWebXmlToWebapp = addDefaultWebXmlToWebapp;
    }


    /*
     * Uses essentially the same logic as {@link ContainerBase#logName()}.
     */
    /*
     * 本质上使用与{@link ContainerBaselogName()}相同的逻辑。
     */
    private String getLoggerName(Host host, String contextName) {
        if (host == null) {
            host = getHost();
        }
        StringBuilder loggerName = new StringBuilder();
        loggerName.append(ContainerBase.class.getName());
        loggerName.append(".[");
        // Engine name
        loggerName.append(host.getParent().getName());
        loggerName.append("].[");
        // Host name
        loggerName.append(host.getName());
        loggerName.append("].[");
        // Context name
        if (contextName == null || contextName.equals("")) {
            loggerName.append('/');
        } else if (contextName.startsWith("##")) {
            loggerName.append('/');
            loggerName.append(contextName);
        }
        loggerName.append(']');

        return loggerName.toString();
    }

    /**
     * Create the configured {@link Context} for the given <code>host</code>.
     * The default constructor of the class that was configured with
     * {@link StandardHost#setContextClass(String)} will be used
     *
     * @param host
     *            host for which the {@link Context} should be created, or
     *            <code>null</code> if default host should be used
     * @param url
     *            path of the webapp which should get the {@link Context}
     * @return newly created {@link Context}
     */
    // 为给定的主机创建配置的Context。
    // 将使用使用StandardHost.setContextClass(String)配置的类的默认构造函数
    // 参数: host -应该为其创建Context的主机，如果使用默认主机则为空
    // url -应该获得上下文的webapp的路径 返回: 新创建的
    private Context createContext(Host host, String url) {
        String defaultContextClass = StandardContext.class.getName();
        String contextClass = StandardContext.class.getName();
        if (host == null) {
            host = this.getHost();
        }
        if (host instanceof StandardHost) {
            contextClass = ((StandardHost) host).getContextClass();
        }
        try {
            if (defaultContextClass.equals(contextClass)) {
                return new StandardContext();
            } else {
                return (Context) Class.forName(contextClass).getConstructor()
                    .newInstance();
            }

        } catch (ReflectiveOperationException  | IllegalArgumentException | SecurityException e) {
            throw new IllegalArgumentException(sm.getString("tomcat.noContextClass", contextClass, host, url), e);
        }
    }

    /**
     * Enables JNDI naming which is disabled by default. Server must implement
     * {@link Lifecycle} in order for the {@link NamingContextListener} to be
     * used.
     *
     */
    // 启用JNDI命名，默认情况下是禁用的。服务器必须实现生命周期才能使用NamingContextListener。
    public void enableNaming() {
        // Make sure getServer() has been called as that is where naming is
        // disabled
        // 确保getServer()已经被调用，因为那是命名被禁用的地方
        getServer();
        server.addLifecycleListener(new NamingContextListener());

        System.setProperty("catalina.useNaming", "true");

        String value = "org.apache.naming";
        String oldValue =
            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
        if (oldValue != null) {
            if (oldValue.contains(value)) {
                value = oldValue;
            } else {
                value = value + ":" + oldValue;
            }
        }
        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);

        value = System.getProperty
            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
        if (value == null) {
            System.setProperty
                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                 "org.apache.naming.java.javaURLContextFactory");
        }
    }


    /**
     * Provide default configuration for a context. This is broadly the
     * programmatic equivalent of the default web.xml and provides the following
     * features:
     * <ul>
     * <li>Default servlet mapped to "/"</li>
     * <li>JSP servlet mapped to "*.jsp" and ""*.jspx"</li>
     * <li>Session timeout of 30 minutes</li>
     * <li>MIME mappings (subset of those in conf/web.xml)</li>
     * <li>Welcome files</li>
     * </ul>
     * TODO: Align the MIME mappings with conf/web.xml - possibly via a common
     *       file.
     *
     * @param contextPath   The path of the context to set the defaults for
     */
    // 为上下文提供默认配置。
    // 这大致相当于默认的web.xml，并提供以下特性:
    // 默认servlet映射到“/”
    // JSP servlet映射到“*.jsp”和“*.jspx”
    // 会话超时30分钟
    // MIME映射(conf/web.xml中那些映射的子集)
    // 欢迎文件
    // 待办事项:将MIME映射与conf/web.xml对齐——可能通过一个公共文件。
    public void initWebappDefaults(String contextPath) {
        Container ctx = getHost().findChild(contextPath);
        initWebappDefaults((Context) ctx);
    }


    /**
     * Static version of {@link #initWebappDefaults(String)}.
     *
     * @param ctx   The context to set the defaults for
     */
    // initWebappDefaults(String)的静态版本。
    // 参数: ctx -设置默认值的上下文
    public static void initWebappDefaults(Context ctx) {
        // Default servlet
        Wrapper servlet = addServlet(
                ctx, "default", "org.apache.catalina.servlets.DefaultServlet");
        servlet.setLoadOnStartup(1);
        servlet.setOverridable(true);

        // JSP servlet (by class name - to avoid loading all deps)
        // JSP servlet(按类名-避免加载所有dep)
        servlet = addServlet(
                ctx, "jsp", "org.apache.jasper.servlet.JspServlet");
        servlet.addInitParameter("fork", "false");
        servlet.setLoadOnStartup(3);
        servlet.setOverridable(true);

        // Servlet mappings
        ctx.addServletMappingDecoded("/", "default");
        ctx.addServletMappingDecoded("*.jsp", "jsp");
        ctx.addServletMappingDecoded("*.jspx", "jsp");

        // Sessions
        ctx.setSessionTimeout(30);

        // MIME type mappings
        addDefaultMimeTypeMappings(ctx);

        // Welcome files
        ctx.addWelcomeFile("index.html");
        ctx.addWelcomeFile("index.htm");
        ctx.addWelcomeFile("index.jsp");
    }


    /**
     * Add the default MIME type mappings to the provide Context.
     *
     * @param context The web application to which the default MIME type
     *                mappings should be added.
     */
    // 将默认MIME类型映射添加到提供上下文。
    // 参数: context -应该添加默认MIME类型映射的web应用程序。
    public static void addDefaultMimeTypeMappings(Context context) {
        Properties defaultMimeMappings = new Properties();
        try (InputStream is = Tomcat.class.getResourceAsStream("MimeTypeMappings.properties")) {
            defaultMimeMappings.load(is);
            for (Map.Entry<Object, Object>  entry: defaultMimeMappings.entrySet()) {
                context.addMimeMapping((String) entry.getKey(), (String) entry.getValue());
            }
        } catch (IOException e) {
            throw new IllegalStateException(sm.getString("tomcat.defaultMimeTypeMappingsFail"), e);
        }
    }


    /**
     * Fix startup sequence - required if you don't use web.xml.
     *
     * <p>
     * The start() method in context will set 'configured' to false - and
     * expects a listener to set it back to true.
     */
    // 修复启动顺序——如果你不使用web.xml，这是必需的。
    // 上下文中的start()方法将'configured'设置为false，并期望侦听器将其设置为true。
    public static class FixContextListener implements LifecycleListener {

        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            try {
                Context context = (Context) event.getLifecycle();
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                    context.setConfigured(true);

                    // Process annotations when not running in a Graal image
                    // annotations require reflections and additional configuration
                    // 当没有在Graal图像中运行时，处理注释需要反射和其他配置
                    if (!JreCompat.isGraalAvailable()) {
                        WebAnnotationSet.loadApplicationAnnotations(context);
                    }

                    // LoginConfig is required to process @ServletSecurity
                    // annotations
                    // 处理@ServletSecurity注释需要LoginConfig
                    if (context.getLoginConfig() == null) {
                        context.setLoginConfig(new LoginConfig("NONE", null, null, null));
                        context.getPipeline().addValve(new NonLoginAuthenticator());
                    }
                }
            } catch (ClassCastException e) {
            }
        }
    }


    /**
     * Fix reload - required if reloading and using programmatic configuration.
     * When a context is reloaded, any programmatic configuration is lost. This
     * listener sets the equivalent of conf/web.xml when the context starts.
     */
    // 修复重新加载和使用编程配置时需要重新加载的问题。
    // 当重新加载上下文时，任何编程配置都将丢失。
    // 当上下文启动时，这个侦听器设置了与conf/web.xml等价的文件。
    public static class DefaultWebXmlListener implements LifecycleListener {
        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
                initWebappDefaults((Context) event.getLifecycle());
            }
        }
    }


    /**
     * Helper class for wrapping existing servlets. This disables servlet
     * lifecycle and normal reloading, but also reduces overhead and provide
     * more direct control over the servlet.
     */
    // 用于包装现有servlet的Helper类。
    // 这将禁用servlet生命周期和正常的重新加载，但也减少了开销，并提供了对servlet的更直接控制。
    public static class ExistingStandardWrapper extends StandardWrapper {
        private final Servlet existing;

        @SuppressWarnings("deprecation")
        public ExistingStandardWrapper( Servlet existing ) {
            this.existing = existing;
            if (existing instanceof javax.servlet.SingleThreadModel) {
                singleThreadModel = true;
                instancePool = new Stack<>();
            }
            this.asyncSupported = hasAsync(existing);
        }

        private static boolean hasAsync(Servlet existing) {
            boolean result = false;
            Class<?> clazz = existing.getClass();
            WebServlet ws = clazz.getAnnotation(WebServlet.class);
            if (ws != null) {
                result = ws.asyncSupported();
            }
            return result;
        }

        @SuppressWarnings("deprecation")
        @Override
        public synchronized Servlet loadServlet() throws ServletException {
            if (singleThreadModel) {
                Servlet instance;
                try {
                    instance = existing.getClass().getConstructor().newInstance();
                } catch (ReflectiveOperationException e) {
                    throw new ServletException(e);
                }
                instance.init(facade);
                return instance;
            } else {
                if (!instanceInitialized) {
                    existing.init(facade);
                    instanceInitialized = true;
                }
                return existing;
            }
        }
        @Override
        public long getAvailable() {
            return 0;
        }
        @Override
        public boolean isUnavailable() {
            return false;
        }
        @Override
        public Servlet getServlet() {
            return existing;
        }
        @Override
        public String getServletClass() {
            return existing.getClass().getName();
        }
    }

    protected URL getWebappConfigFile(String path, String contextName) {
        File docBase = new File(path);
        if (docBase.isDirectory()) {
            return getWebappConfigFileFromDirectory(docBase, contextName);
        } else {
            return getWebappConfigFileFromWar(docBase, contextName);
        }
    }

    private URL getWebappConfigFileFromDirectory(File docBase, String contextName) {
        URL result = null;
        File webAppContextXml = new File(docBase, Constants.ApplicationContextXml);
        if (webAppContextXml.exists()) {
            try {
                result = webAppContextXml.toURI().toURL();
            } catch (MalformedURLException e) {
                Logger.getLogger(getLoggerName(getHost(), contextName)).log(Level.WARNING,
                        sm.getString("tomcat.noContextXml", docBase), e);
            }
        }
        return result;
    }

    private URL getWebappConfigFileFromWar(File docBase, String contextName) {
        URL result = null;
        try (JarFile jar = new JarFile(docBase)) {
            JarEntry entry = jar.getJarEntry(Constants.ApplicationContextXml);
            if (entry != null) {
                result = UriUtil.buildJarUrl(docBase, Constants.ApplicationContextXml);
            }
        } catch (IOException e) {
            Logger.getLogger(getLoggerName(getHost(), contextName)).log(Level.WARNING,
                    sm.getString("tomcat.noContextXml", docBase), e);
        }
        return result;
    }

    static {
        // Graal native images don't load any configuration except the VM default
        // Graal本机映像不加载虚拟机默认值以外的任何配置
        if (JreCompat.isGraalAvailable()) {
            try (InputStream is = new FileInputStream(new File(System.getProperty("java.util.logging.config.file", "conf/logging.properties")))) {
                LogManager.getLogManager().readConfiguration(is);
            } catch (SecurityException | IOException e) {
                // Ignore, the VM default will be used
                // 忽略，将使用虚拟机默认值
            }
        }
    }

    /**
     * Main executable method for use with a Maven packager.
     * @param args the command line arguments
     * @throws Exception if an error occurs
     */
    // 与Maven打包器一起使用的主要可执行方法。
    // 参数: Args -命令行参数 抛出: 异常-如果发生错误
    public static void main(String[] args) throws Exception {
        // Process some command line parameters
        // 处理一些命令行参数
        String[] catalinaArguments = null;
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("--no-jmx")) {
                Registry.disableRegistry();
            } else if (args[i].equals("--catalina")) {
                // This was already processed before
                // Skip the rest of the arguments as they are for Catalina
                // 这已经处理好了跳过其余的论点，因为他们是为Catalina
                ArrayList<String> result = new ArrayList<>();
                for (int j = i + 1; j < args.length; j++) {
                    result.add(args[j]);
                }
                catalinaArguments = result.toArray(new String[0]);
                break;
            }
        }
        SecurityClassLoad.securityClassLoad(Thread.currentThread().getContextClassLoader());
        org.apache.catalina.startup.Tomcat tomcat = new org.apache.catalina.startup.Tomcat();
        // Create a Catalina instance and let it parse the configuration files
        // It will also set a shutdown hook to stop the Server when needed
        // Use the default configuration source
        // 创建一个Catalina实例，并让它解析配置文件。
        // 它还将设置一个shutdown钩子，以便在需要时停止服务器
        tomcat.init(null, catalinaArguments);
        boolean await = false;
        String path = "";
        // Process command line parameters
        // 处理命令行参数
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("--war")) {
                if (++i >= args.length) {
                    throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i - 1]));
                }
                File war = new File(args[i]);
                tomcat.addWebapp(path, war.getAbsolutePath());
            } else if (args[i].equals("--path")) {
                if (++i >= args.length) {
                    throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i - 1]));
                }
                path = args[i];
            } else if (args[i].equals("--await")) {
                await = true;
            } else if (args[i].equals("--no-jmx")) {
                // This was already processed before
                // 这个之前已经处理过了
            } else if (args[i].equals("--catalina")) {
                // 这已经处理好了跳过其余的论点，因为他们是为卡特琳娜
                break;
            } else {
                throw new IllegalArgumentException(sm.getString("tomcat.invalidCommandLine", args[i]));
            }
        }
        tomcat.start();
        // Ideally the utility threads are non daemon
        // 理想情况下，实用程序线程不是守护进程
        if (await) {
            tomcat.getServer().await();
        }
    }

}
